/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.stetho.dumpapp.plugins;

import android.content.Context;
import android.os.Debug;

import com.facebook.stetho.common.Util;
import com.facebook.stetho.dumpapp.DumpException;
import com.facebook.stetho.dumpapp.DumpUsageException;
import com.facebook.stetho.dumpapp.DumperContext;
import com.facebook.stetho.dumpapp.DumperPlugin;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.Iterator;

/**
 * Generates an hprof on the sdcard and returns to the client the absolute path to the file.
 * <p/>
 * For convenient usage, see: {@code scripts/hprof_dump.sh}
 */
public class HprofDumperPlugin implements DumperPlugin {

  private static final String NAME = "hprof";
  private final Context mContext;

  public HprofDumperPlugin(Context context) {
    mContext = context;
  }

  @Override
  public String getName() {
    return NAME;
  }

  @Override
  public void dump(DumperContext dumpContext) throws DumpException {
    final PrintStream output = dumpContext.getStdout();

    Iterator<String> argsIter = dumpContext.getArgsAsList().iterator();
    String outputPath = argsIter.hasNext() ? argsIter.next() : null;
    if (outputPath == null) {
      usage(output);
    } else {
      if ("-".equals(outputPath)) {
        handlePipeOutput(output);
      } else {
        File outputFile = new File(outputPath);
        if (!outputFile.isAbsolute()) {
          outputFile = mContext.getFileStreamPath(outputPath);
        }
        writeHprof(outputFile);
        output.println("Wrote to " + outputFile);
      }
    }
  }

  private void handlePipeOutput(OutputStream output) throws DumpException {
    File hprofFile = mContext.getFileStreamPath("hprof-dump.hprof");
    try {
      writeHprof(hprofFile);
      try {
        InputStream input = new FileInputStream(hprofFile);
        try {
          Util.copy(input, output, new byte[2048]);
        } finally {
          input.close();
        }
      } catch (IOException e) {
        throw new DumpException("Failure copying " + hprofFile + " to dumper output");
      }
    } finally {
      if (hprofFile.exists()) {
        hprofFile.delete();
      }
    }
  }

  private void writeHprof(File outputPath) throws DumpException {
    try {
      // Test that we can write here.  dumpHprofData appears to hang if it cannot write
      // to the target location on ART.
      truncateAndDeleteFile(outputPath);
      Debug.dumpHprofData(outputPath.getAbsolutePath());
    } catch (IOException e) {
      throw new DumpException("Failure writing to " + outputPath + ": " + e.getMessage());
    }
  }

  private static void truncateAndDeleteFile(File file) throws IOException {
    FileOutputStream out = new FileOutputStream(file);
    out.close();
    if (!file.delete()) {
      throw new IOException("Failed to delete " + file);
    }
  }

  private void usage(PrintStream output) throws DumpUsageException {
    output.println("Usage: dumpapp hprof [ path ]");
    output.println("Dump HPROF memory usage data from the running application.");
    output.println();
    output.println("Where path can be any of:");
    output.println("  -           Output directly to stdout");
    output.println("  <path>      Full path to a writable file on the device");
    output.println("  <filename>  Relative filename that will be stored in the app internal storage");
    throw new DumpUsageException("Missing path");
  }
}
